package com.aichan.aibi.service.impl;

import static com.aichan.aibi.constant.RedisConstant.USER_LOGIN;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpStatus;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.aichan.aibi.common.ErrorCode;
import com.aichan.aibi.constant.RedisConstant;
import com.aichan.aibi.exception.BusinessException;
import com.aichan.aibi.exception.ThrowUtils;
import com.aichan.aibi.mapper.RoleMapper;
import com.aichan.aibi.mapper.UserMapper;
import com.aichan.aibi.mapper.UserRoleMapper;
import com.aichan.aibi.model.dto.user.UserUpdateRequest;
import com.aichan.aibi.model.entity.User;
import com.aichan.aibi.model.entity.login.LoginUser;
import com.aichan.aibi.model.entity.relevancy.UserRole;
import com.aichan.aibi.model.enums.UserRoleEnum;
import com.aichan.aibi.model.vo.LoginUserVO;
import com.aichan.aibi.properties.JwtProperties;
import com.aichan.aibi.service.UserService;
import com.aichan.aibi.utils.JwtUtil;
import com.aichan.aibi.utils.RedisCache;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author mengyu
 * @date 2024/03/03 9:26
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Resource
    private JwtProperties jwtProperties;
    @Resource
    private PasswordEncoder passwordEncoder;
    @Resource
    private AuthenticationManager authenticationManager;
    @Resource
    private RedisCache redisCache;
    @Resource
    private UserRoleMapper userRoleMapper;
    @Resource
    private RoleMapper roleMapper;

    @Override
    @Transactional
    public long userRegister(String userAccount, String userPassword, String checkPassword) {
        synchronized (userAccount.intern()) {
            // 账户不能重复
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("userAccount", userAccount);
            long count = this.baseMapper.selectCount(queryWrapper);
            if (count > 0) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号重复");
            }
            // 2. 加密
            String encryptPassword = passwordEncoder.encode(jwtProperties.getSalt() + userPassword);
            // 3. 插入数据
            User user = new User();
            user.setUserAccount(userAccount);
            user.setUserPassword(encryptPassword);
            user.setUserName(userAccount);
            user.setUserRole(UserRoleEnum.user.getText());
            boolean saveResult = this.save(user);
            // 4. 插入角色和权限数据
            UserRole userRole = new UserRole();
            userRole.setUserId(user.getId());
            userRole.setRoleId(UserRoleEnum.user.getValue());
            int insert = userRoleMapper.insert(userRole);
            // 5. 判断是否新增成功
            if (insert == 0 || !saveResult) {
                throw new BusinessException(ErrorCode.SYSTEM_ERROR, "注册失败，数据库错误");
            }
            return user.getId();
        }
    }

    @Override
    public LoginUserVO userLogin(String userAccount, String userPassword) {
        // 1. 登录验证
        UsernamePasswordAuthenticationToken upa = new UsernamePasswordAuthenticationToken(userAccount, jwtProperties.getSalt() + userPassword);
        Authentication authenticate = authenticationManager.authenticate(upa);
        if (Objects.isNull(authenticate)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户名或密码错误");
        }
        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
        if (Objects.isNull(loginUser)) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "登录失败");
        }
        // 2. 将user信息存入Redis
        Long id = loginUser.getUser().getId();
        redisCache.setCacheObject(USER_LOGIN + id, loginUser, jwtProperties.getUserTtl(), TimeUnit.MINUTES);
        // 3. 生成JWT Token
        String token = "";
        try {
            token = JwtUtil.createJWT(jwtProperties.getUserSecretKey(), loginUser.getUser().getId(), Long.valueOf(jwtProperties.getUserTtl()));
        } catch (Exception e) {
            log.error("error: {}", e.getMessage());
            redisCache.deleteObject("login:" + id);
        }
        // 4. 返回
        return LoginUserVO.getLoginUserVO(loginUser.getUser(), token, loginUser.getUser().getUserRole());
    }

    /**
     * 获取当前登录用户
     *
     * @param request
     * @return
     */
    @Override
    public User getLoginUser(HttpServletRequest request) {
        // 先判断是否已登录
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (Objects.isNull(authentication)) {
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
        }
        LoginUser user = (LoginUser) authentication.getPrincipal();
        return user.getUser();
    }

    /**
     * 用户忘记密码
     *
     * @param email
     * @param password
     * @param checkPassword
     * @param code
     * @return
     */
    @Override
    public Long userForgetPassword(String email, String password, String checkPassword, Integer code) {
//        ThrowUtils.throwIf(!password.equals(checkPassword), ErrorCode.OPERATION_ERROR, "两次输入的密码不一致");
//        ThrowUtils.throwIf(!redisCache.exist(RedisConstant.USER_REG_CODE + email), ErrorCode.OPERATION_ERROR, "验证码已过期");
//        Integer codeJudge = redisCache.getCacheObject(RedisConstant.USER_REG_CODE + email);
//        ThrowUtils.throwIf(!Objects.equals(code, codeJudge), ErrorCode.OPERATION_ERROR, "验证码错误");
//        ThrowUtils.throwIf(user == null, ErrorCode.OPERATION_ERROR, "用户不存在");
//        user.setUserPassword(passwordEncoder.encode(jwtProperties.getSalt() + password));
//        int update = this.baseMapper.updateById(user);
//        ThrowUtils.throwIf(update == 0, ErrorCode.SYSTEM_ERROR, "系统异常");
//        return user.getId();
        return null;
    }


    /**
     * 用户注销
     */
    @Override
    @Transactional
    public void logout() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        User user = (User) authentication.getPrincipal();
        Long userId = user.getId();
        try {
            // redis
            redisCache.deleteObject("login:" + userId);
            // SecurityContext
            SecurityContextHolder.clearContext();
        } catch (Exception e) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "登出失败");
        }
    }

    @Override
    public LoginUserVO getLoginUserVO(String token) {
        Long userId = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token);
        Boolean exist = redisCache.exist(USER_LOGIN + userId);
        if (!exist) {
            throw new BusinessException(HttpStatus.HTTP_FORBIDDEN, "未登录或登录过期");
        }
        LoginUser loginUser = redisCache.getCacheObject(USER_LOGIN + userId);
        return LoginUserVO.getLoginUserVO(loginUser.getUser(), token, roleMapper.getRoleByUserId(loginUser.getUser().getId()));
    }

    @Override
    @Transactional
    public User addUser(String userAccount, String password, String email, String role) {
        User user = new User();
        user.setUserAccount(userAccount);
        user.setUserPassword(passwordEncoder.encode(jwtProperties.getSalt() + password));
        user.setUserRole(role);
        user.setUserName(userAccount);
        int insert = this.baseMapper.insert(user);
        UserRole userRole = new UserRole();
        userRole.setUserId(user.getId());
        userRole.setRoleId(UserRoleEnum.valueOf(role).getValue());
        int insert1 = userRoleMapper.insert(userRole);
        ThrowUtils.throwIf(insert1 * insert == 0, ErrorCode.SYSTEM_ERROR, "数据库异常");
        return user;
    }

    @Override
    @Transactional
    public void updateUser(UserUpdateRequest userUpdateRequest) {
        User user = this.baseMapper.selectById(userUpdateRequest.getId());
        BeanUtils.copyProperties(userUpdateRequest, user);
        int result = this.baseMapper.updateById(user);
        ThrowUtils.throwIf(result == 0, ErrorCode.SYSTEM_ERROR, "数据库异常");
        String userRole = userUpdateRequest.getUserRole();
        if (StrUtil.isNotBlank(userRole)) {
            LambdaQueryWrapper<UserRole> lqw = new LambdaQueryWrapper<UserRole>().eq(UserRole::getUserId, user.getId());
            int delete = userRoleMapper.delete(lqw);
            UserRole userRole1 = new UserRole();
            userRole1.setUserId(user.getId());
            userRole1.setRoleId(UserRoleEnum.valueOf(userRole).getValue());
            int i = userRoleMapper.insert(userRole1);
            ThrowUtils.throwIf(i * delete == 0, ErrorCode.SYSTEM_ERROR, "数据库异常");
        }
    }

    @Override
    @Transactional
    public void deleteUser(Long id) {
        int i = this.baseMapper.deleteById(id);
        LambdaQueryWrapper<UserRole> lqw = new LambdaQueryWrapper<UserRole>().eq(UserRole::getUserId, id);
        int delete = userRoleMapper.delete(lqw);
        ThrowUtils.throwIf(i * delete == 0, ErrorCode.SYSTEM_ERROR, "数据库异常");
    }

    @Override
    public Page<User> listUserByPage(Long id, String userName, String userRole, long current, long size) {
        LambdaQueryWrapper<User> ulqw = new LambdaQueryWrapper<>();
        ulqw.eq(id != null, User::getId, id)
                .eq(!StrUtil.isBlankIfStr(userName), User::getUserName, userName)
                .orderBy(true, true, User::getCreateTime)
        ;
        return this.page(new Page<>(current, size),
                ulqw);
    }
}
